/*LICENSE_BEGIN
 * ******************************************************************
 * Copyright 2006 by the Dainamite Robocup-Team at the
 * DAI-Labor, TU-Berlin (www.dai-labor.de)
 * 
 * Members of Dainamite are:
 * Holger Endert
 * Robert Wetzker
 * Thomas Karbe
 * Axel Heßler
 * Philippe Bütter
 * Felix Brossmann
 * 
 * Licensed under the GNU GPL (General Public License) Free Software 
 * License, Version 2. You may use this file only in compliance with 
 * the License. You may obtain a copy of the License at:
 * 
 * http://www.fsf.org/licensing/licenses/gpl.txt
 * ******************************************************************
LICENSE_END*/
package robocup.component;

import java.lang.reflect.Constructor;
import java.util.LinkedList;

import robocup.component.ControllerNumbers.PLAY_MODE;
import robocup.component.infotypes.AuralInfo;
import robocup.component.infotypes.Info;
import robocup.component.infotypes.PlayerSayInfo;
import robocup.component.infotypes.PlayerTypesInfo;
import robocup.component.math.RCMath;
import robocup.component.speechacts.MessageFactory;
import robocup.component.tactics.AbstractState;
import robocup.component.tactics.StateEvaluation;
import robocup.component.tactics.AbstractState.STATES;
import robocup.component.worldobjects.Player;
import robocup.connection.TeamSoccerScopeMediator;

/**
 * This is the main class for all tactical decisions. It refers to
 * the StateEvaluation of each agent, and triggers these, if
 * new information is received. It also instantiates all states out
 * of the state-property specified in the agent-property-file given
 * on startup to the agent by using reflection.
 */
public class StateTactic {

    /**
     * Reference to the agent object.
     */
    private RobocupAgent agent;
    /**
     * The agents prophet instance.
     */
    private Prophet prophet;
    /**
     * <code>savedStates</code> is used to memorize the executed states for an
     * agent in order to display them on the monitor, if this is
     * used.
     */
    private LinkedList<String> savedStates = new LinkedList<String>();
    /**
     * <code>buffer</code> is used to generate the string of
     * <code>savedStates</code>.
     */
    private StringBuffer buffer = new StringBuffer();

    /**
     * Constructor of the StateTactic, needs references of the agent
     * (RobocupAgent) and the prophet.
     *
     * @param agent -
     *            the agent object (parent)
     * @param prophet -
     *            the predicting component
     */
    public StateTactic(RobocupAgent agent, Prophet prophet) {
        this.agent = agent;
        this.prophet = prophet;
    }

    /**
     * <code>setStates</code> sets all the states plus their allowed play-modes
     * of the conf file to the self player and adds the states to the state
     * evaluation.
     *
     * @param strings - an array containing the string-names of the states.
     * Note that the source-package of all states is <code>robocup.component.tactic
     * </code>, such that the state-names should be specified relative to that
     * path.
     */
    public void setStates(String[] strings) {
        Player self = this.agent.getWorld().getSelfRef();
        StateEvaluation myStateEval = new StateEvaluation(this, this.agent);

        PLAY_MODE pm;
        STATES lastState = null;

        for (String key : strings) {

            try {
                pm = PLAY_MODE.valueOf(key);
            } catch (IllegalArgumentException e) {
                pm = PLAY_MODE.UNKNOWN;
            }

            if (pm == PLAY_MODE.UNKNOWN) {
                Class<?> cls = null;
                //zmena oproti dainamite: taktiku treba zadavat s fully classified
                //name do konfiguraku aby tu nebola natvrdo package
                try {
                    cls = Class.forName(key);
                    System.out.println("Uspesne som naloadoval stav: " + key);
                } catch (ClassNotFoundException e) {
                    System.out.println("Nenasiel som stav: " + key);
                    continue;
                }
               
                // Got class, now get its constructor
                Constructor constructor;
                try {
                    // Is there a constuctor ?
                    constructor = cls.getConstructor(StateEvaluation.class);
                } catch (Exception e2) {
                    System.out.println("Could not get constructor for state: " + key);
                    e2.printStackTrace();
                    continue;
                }
                AbstractState newState;
                try {
                    newState = (AbstractState) constructor.newInstance(myStateEval);
                    lastState = newState.getState();
                    self.addKnownState(lastState, new LinkedList<PLAY_MODE>());
                    myStateEval.addState(newState);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } else {
                if (lastState == null) {
                    System.out.println(this.getAgent().getWorld().getCycleAndPlayerString() + " StateTactic: Got PlayMode without state: " + pm);
                    continue;
                }
                self.addPlayModeToState(lastState, pm);
            }
        }

        self.setMyStateEval(myStateEval);

    }

    /**
     * Forwards the info to the StateEvaluation. Is currently not used,
     * because aural infos do not trigger an action.
     */
    public void receivedAuralInfo(AuralInfo info) {
        if (info.getInfoType() == Info.TYPE_AURAL_PSI) {
            MessageFactory mf = new MessageFactory();
        }
    }

    /**
     * Triggers the StateEvaluation. Visual information should be
     * the basis on which actions were determined.
     */
    public void receivedVisualInfo() {
        if (this.agent.getWorld().getSelfRef().getMyStateEval() != null) {
            this.calculateBestState();
        }
    }

    /**
     * Triggers the StateEvaluation. Note that not always a sense-body info
     * is used to determine an action. The decision is made in
     * <code>RobocupAgent.processInfo(info)</code>, and depends on the <code>
     * Synchro</code>.
     */
    public void receivedSenseInfo() {
        if (this.agent.getWorld().getSelfRef().getMyStateEval() != null) {
            this.calculateBestState();
        }
    }

    /**
     * Called when the init info is received, and invokes the creation of states
     * out of the property-file.
     */
    public void receivedInitInfo() {
        this.setStates(this.agent.getStates());
    }

    /**
     * Nothing todo here up to now!
     *
     * @param info -
     *            the player type info
     */
    public void receivedPlayerTypeInf(PlayerTypesInfo info) {
    }

    /**
     * Calculates the best state using the state evaluation and executes the
     * state.
     */
    private void calculateBestState() {
        this.agent.getWorld().getSelfRef().calculateBestState(
                this.agent.getWorld().getBallRef());

        AbstractState bestState = this.agent.getWorld().getSelfRef().getMyStateEval().getBestState();
        if (bestState != null) {
            bestState.setSubStateString();
        }
        this.saveStates();

        this.executeState(bestState);
    }

    /**
     * This method executes the selected state by calling the methods for
     * calculating all possible action types and then sending those to the
     * synchro! The order is:
     * <ol>
     *   <li>calculateMainAction()
     *   <li>calculateViewModeAction()
     *   <li>calculateAttentionToAction()
     *   <li>calculateTurnNeckAction()
     *   <li><it>calculatePointToAction()</it> - this is currently not used
     *   <li>calculateSayAction()
     * </ol>
     *
     * Note that the order is important, because there exist depenedencies
     * from previous calculated actions to the following ones.
     *
     * @param state -
     *            the state to execute
     */
    public void executeState(AbstractState state) {
        if (state == null) {
            return;
        }

        state.resetActions();
        state.calculateMainAction();
        state.calculateViewModeAction();
        state.calculateAttentionToAction();
        state.calculateTurnNeckAction();
        // state.calculatePointToAction();
        state.calculateSayAction();

        Synchro synchro = this.getAgent().getSynchro();
        synchro.setMainAction(state.getMainAction());
        synchro.setTurnNeckAction(state.getTurnNeckAction());
        synchro.setViewModeAction(state.getViewModeAction());
        synchro.setSayAction(state.getSayAction());
        synchro.setAttentionToAction(state.getAttentionToAction());
        synchro.setPointToAction(state.getPointAtAction());

        this.getAgent().informFinishedActing();
    }

    /**
     * Saves the actual state-evaluation process (state-name, assessment,
     * precondition) to a string-list, which is used monitor the decisions
     * of an agent on the gui.
     */
    private void saveStates() {
        this.savedStates.clear();

        for (AbstractState state : this.agent.getWorld().getSelfRef().getMyStateEval().getPossibleStates()) {
            this.buffer.setLength(0);
            this.buffer.append(
                    Double.toString(RCMath.round(state.getStateAssessment(),
                    3))).append("\t").append(
                    state.getState().toString()).append("(").append(
                    state.getSubStateString()).append(")");
            this.savedStates.add(this.buffer.toString());
        }
    }

    /**
     * Adds all possible states to the gui. Called by the RobocupAgent after
     * sending actions.
     */
    public void addStatesToGUI() {
        for (String s : this.savedStates) {
            TeamSoccerScopeMediator.registerAddStateAss(s, this.getAgent());
        }
    }

    /**
     *
     * @return
     * 	Returns a reference to the agent itself.
     */
    public RobocupAgent getAgent() {
        return this.agent;
    }

    /**
     * @return Returns a reference to the prophet.
     */
    public Prophet getProphet() {
        return this.prophet;
    }
}
